# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
from utils.selenium_helper import get_driver, deviceType, driverType, driver_headless
from GuaZi_Spider.items import *
import re, requests, urllib.parse,time,datetime
from utils.proxy_helper import getProxyHost
from utils.js_util import decrypt_anti_info_cookie
from fake_useragent import UserAgent

class GuaziSpider(scrapy.Spider):
	name = 'guazi'
	allowed_domains = ['www.guazi.com']
	start_urls = ['https://www.guazi.com/www/buy']

	def __init__(self):
		'''
		构造函数，爬虫的初始化操作
		'''
		scrapy.Spider.__init__(self, self.name)

		# 输入关键字
		self.keyword = "现代"
		# self.keyword = input("开始起动瓜子网爬虫,请输入要爬取的关键字:")
		print(f"开始起动瓜子网爬虫,请输入要爬取的关键字:{self.keyword}")

		# 对搜索 关键字 编码
		url_encoding_keyword = urllib.parse.quote(self.keyword)
		self.search_keyword_url = f"https://www.guazi.com/www/buy/_{url_encoding_keyword}/?input=1"


		# 定义 有 2种 方式 实现（mode = 1 : 只使用 scrapy 爬取 、mode = 2：使用 scrapy + selenium 爬取）
		# 修改mode 值 改变 爬取方式！！
		self.mode = 1

		# 是否要 启用 代理
		self.enabled_proxy = True

		# 初始化默认值
		self.proxy_host = None
		self.proxies = None
		self.useragent = None
		self._antipas_cookie = None
		self.last_update_time = None # 最后更新 antipas_cookie 的时间
		self.driver = None

		if self.mode == 1:# 只使用 scrapy 爬取
			# 初始化 设置代理 以及 设置antipas_cookie
			self.init_proxy_antipas()

		if self.proxy_host:
			print(f"获取到代理：{self.proxy_host}")

		if self._antipas_cookie:
			print(f"antipas_cookie:{self._antipas_cookie}")

		if self.mode == 1:
			# 通过 url 实现 关键字搜索

			"""
				https://blog.csdn.net/okm6666/article/details/88318266
				https://blog.csdn.net/weixin_42812527/article/details/89478382
				
				瓜子二手车 有反爬机制过程：
					1. 清除浏览器所有cookie， 第一次请求瓜子网，没有cookie, 服务器端会检测 请求头 有没有携带 antipas 的cookie （注意：服务器 处理用户首次访问  一般会走类似 Session_Start 的 请求管道）;
					反爬机制：首次访问 服务器不检测 已经写入的cookie, 而只检测请求头的cookie !!!
					2. 服务器 发现你 首次访问 请求头没有携带 antipas cookie 后，然后让代码去执行 一个页面（包含有：正在打开中,请稍后...），这个页面中有3个作用：
							1）对 js方式实现的cookie值的算法脚本进行加密
							2）js 写入 antipas 的cookie
							3）location.replace(url) 跳转替换响应当前请求   注：scrapy 去爬这个时候，就会因为 服务器端 Response 203， 而没有得到自己想要返回的页面内容
							
					3. 跳转之后，这个第2次请求，携带了  antipas 的cookie，这次服务器检测到这个请求 不是首次请求，并且携带有 antipas ，然后又对 antipas 的值 进行算法验证，验证OK，则正常返回数据，验证失败则 Response 203 。
				
				结论：首先想法 找到生成 antipas 的算法，进行伪造。
			"""
			self.start_urls[0] = self.search_keyword_url
		else:
			# 通过 启动 selenium 浏览器，键入搜索框关键字，进行搜索

			# 是否启用代理
			if self.enabled_proxy:
				# 使用代理
				self.proxy_host = getProxyHost()

			self.driver = get_driver(self.start_urls[0], proxyHost=self.proxy_host, autoOpenDevTools=False,
			                         device_type=deviceType.PC, driver_type=driverType.Firefox)
			# 设置页面 加载时间
			self.driver.set_page_load_timeout(60)
			# 查找元素 超时时间
			self.driver.implicitly_wait(60)

			#检测 弹窗，有弹窗则关闭
			try:
				dialog = self.driver.find_elements_by_class_name("js-close-finance-pop")
				for dia in dialog:
					if dia.is_displayed():
						dia.click()
			except Exception as ex:
				print(ex)


	@property
	def antipas_cookie(self):
		"""
		维护 代理池、ua 池， 每个代理IP 关联要 antipas_cookie 同时进行更新
		:return:
		"""
		cookie = self._antipas_cookie
		curr_time = datetime.datetime.now()
		if (curr_time - self.last_update_time).seconds > 50: #代理时效为1分钟，这里 快到失效期 则 更新
			self.init_proxy_antipas()
			cookie = self._antipas_cookie
			print("自动更换 proxy/useragent ......")
		return cookie


	def init_proxy_antipas(self):
		"""
		访问第1次通过正则拿到变化的 anti_info 值并解密得到每次访问的 cookie 值
		:return:
		"""
		while True:
			try:
				# 设置 userAgent
				self.useragent = UserAgent().chrome

				# 发现：第1次请求 和 其他继续的请求 必须是同一个 userAgent ！！！, 所以这里 和 scrapy 默认的 userAgent 相同，不需使用 fake_useragent
				first_headers = {
					'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
					'User-Agent': self.useragent
				}

				# 是否启用代理
				if self.enabled_proxy:
					# 使用代理
					self.proxy_host = getProxyHost()
					if self.proxy_host:
						self.proxies = {'http': "http://" + self.proxy_host, 'https': "https://" + self.proxy_host}

				# 获取 antipas的cookie
				resp = requests.get(self.search_keyword_url, params=None, proxies=self.proxies, headers=first_headers)
				anti_info = re.findall(r".*(var value=anti.*;)", resp.text)[0]
				cookie_info = decrypt_anti_info_cookie(anti_info)
				# 设置 antipas 的cookie
				self._antipas_cookie = cookie_info
				self.last_update_time = datetime.datetime.now()
				break
			except Exception as ex:
				print(ex)
				time.sleep(2)
				continue



	def __del__(self):
		'''
		析构函数，关闭浏览器
		:return:
		'''
		if self.mode != 1 and self.driver:
			self.driver.close()


	def start_requests(self):
		'''
		重写start_requests, 保证start_urls进行预处理
		:return:
		'''
		for url in self.start_urls:
			req = Request(url,
			              meta={'type': "search_keyword", 'keyword': self.keyword},  # 传入搜索关键字，如果是 mode =2 ,则需要使用
			              callback=self.parse_search_keyword,
			              dont_filter=True)  # 表示调度程序不应过滤此请求。当您想要多次执行相同的请求时，可以使用此选项来忽略重复过滤器。小心使用它，否则您将进入爬行循环。默认为False。
			yield req




	def parse_search_keyword(self, response):
		url = response.url
		try:
			if response.status != 200:
				# 可能 ip 被封 需要使用代理。
				print("可能 ip 被封 需要使用代理!!!")
				pass
			else:
				items = response.css("ul.carlist > li")
				if items:
					for item in items:
						detail_url = "https://www.guazi.com" + item.xpath("./a/@href").extract_first()
						print("详情页：" + detail_url)
						req = Request(detail_url,
						              meta={'type': "get_detail"},
						              # 设置 标记数据，用于 下载中间件 的 process_request 请求前 获取标记数据
						              callback=self.parse_detail,
						              dont_filter=True)  # 表示调度程序不应过滤此请求。当您想要多次执行相同的请求时，可以使用此选项来忽略重复过滤器。小心使用它，否则您将进入爬行循环。默认为False。
						yield req

				next_page_href = response.css(".pageBox .pageLink .next::attr(href)").extract_first()
				# /www/buy/o2c-1/_大众/#bread
				if next_page_href:
					next_page_href = "https://www.guazi.com" + next_page_href
					# 打印下一页 页码：
					next_page_num = int(re.search('o(\d+)c', next_page_href).group().strip('oc'))
					print(f"下一页({next_page_num})：" + next_page_href)

					req = Request(next_page_href,
					              meta={'type': "nextpage", 'url': next_page_href},
					              # 设置 标记数据，用于 下载中间件 的 process_request 请求前 获取标记数据
					              callback=self.parse_search_keyword,
					              dont_filter=True)  # 表示调度程序不应过滤此请求。当您想要多次执行相同的请求时，可以使用此选项来忽略重复过滤器。小心使用它，否则您将进入爬行循环。默认为False。
					yield req
		except Exception as ex:
			print(f"parse_search_keyword出现异常({url})：{ex}")
			# req = Request(url,
			#               meta= response.request.meta,
			#               callback=self.parse_search_keyword,
			#               dont_filter=True)
			yield response.request


	def parse_detail(self, response):
		url = response.url  # 详情url
		try:
			title = response.css(".product-textbox .titlebox::text").extract_first().strip()  # 标题
			licensing_time = response.css(".basic-eleven .one .typebox::text").extract_first()  # 上牌时间
			mileage = response.css(".basic-eleven .two .typebox::text").extract_first()  # 表显里程
			displacement = response.css(".basic-eleven .six .typebox::text").extract_first()  # 排量
			gearbox = response.css(".basic-eleven .five .typebox::text").extract_first()  # 变速箱
			img_src = response.css(".js-bigpic[data-index='0'] img:first-child::attr(src)").extract_first()
			sale_price = response.css(".pricebox .pricestype::text").extract_first().strip()  # 价格
			year_check = response.css(".basic-eleven .nine .typebox::text").extract_first()  # 年检到期
			insurance = response.css(".basic-eleven .ten .typebox::text").extract_first()  # 交强险
			store = response.css(".baomai-right h3::text").extract_first()  # 门店
			look_addr = response.css(".baomai-right p::text").extract_first()  # 看车地点
			owner = response.css(".people-infor span[class='f20']::text").extract_first()  # 车主

			car = CarItem(detail_url=url, title=title, licensing_time=licensing_time, mileage=mileage,
			              displacement=displacement, gearbox=gearbox, img_src=img_src, sale_price=sale_price,
			              year_check=year_check,
			              insurance=insurance, store=store, look_addr=look_addr, owner=owner
			              )
			print(car)
			print("---------------------------------------------------------------------------------------------------------------------------------------------------")
			yield car
		except Exception as ex:
			print(f"parse_detail出现异常({url})：{ex}")
			req = Request(url,
			              meta={'type': "get_detail"},
			              callback=self.parse_detail,
			              dont_filter=True)
			yield req

